package test.client.thread.login;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;

import test.client.support.time.TimeTracker;
import test.client.thread.ClientThread;
import test.domain.environment.MetaUser;

public class ClientLoginThread extends ClientThread implements RosterListener {
	private MetaUser meta;
	private TimeTracker tracker;
	
	private boolean isAllPresenceReceived = false;
	private Roster roster;
	
	private int maxPresenceCount = 0;
	private Map<String, Presence.Type> presences = new ConcurrentHashMap<String, Presence.Type>();

	/*
	 * Test conditions
	 */
	private final String observer = null;
	private final boolean presenceIncluded = true;
	private final boolean sendPresence = false;
	
	@Override
	public void run() {
		tracker = this.addTimeTracker();
		
		int id = this.getId();
		boolean failed = false;
		
		try {
			this.notifyStarted();
			
			tracker.start();
			
			this.login();
			this.setMessage("logged in");
			log.debug(this.getUsername() + ": logged in... Max presence : " + this.maxPresenceCount);
			
			if (sendPresence) {
				Thread.sleep(1000);
				this.sendPresence(Presence.Type.available, "online");	
			}
			
			// Get rosters
			roster = connection.getRoster();
			processRoster();
			this.setMessage("get rosters");
			
			// Validate roster
			if (roster.getEntryCount() < meta.getMaxRosterCount())
				throw new IllegalStateException("Roster info could not be retrieved successfully. Thread ID: " + id);
			
			if (presenceIncluded) {
				// Wait other presences
				if (waitPresenceArrival()) {
					this.setResult(Result.FAILURE);
				}
				else {
					this.setResult(Result.SUCCESS);	
				}
			}
			else {
				tracker.stop();
				this.setResult(Result.SUCCESS);	
			}
			
			// disconnect after 10 sec.
			Thread.sleep(10000);
		} catch (Exception e) {
			log.info(this.getUsername() + ": Error occured!!");
			this.setResult(Result.FAILURE);
			this.setMessage("ERROR: " + e.getMessage());
			failed = true;
		}
		finally {
			if (!failed) this.setMessage("disconnecting...");
			this.disconnect();
			this.setStatus(Status.COMPLETED);
			
			log.debug(this.getUsername() + " thread is completed.");
			
			this.notifyStopped();
		}
	}
	
	/**
	 * Wait presence information sent from other test threads. All presence information received, true value is returned.
	 * If the test doesn't include presence, false value is returned immediately.
	 * @return if test failed, return false. Otherwise return true.
	 * @throws InterruptedException
	 */
	private boolean waitPresenceArrival() throws InterruptedException  {
		// If a test doesn't include received presence, return
		if (!presenceIncluded) return false;
		
		boolean failed = false;
		while (this.getStatus() == Status.RUNNING && !this.isAllPresenceReceived) {
			if (this.getCurrentPresenceCount() == this.maxPresenceCount) {
				this.isAllPresenceReceived = true;
				this.tracker.stop();
				break;
			}
			
			log.debug(this.getUsername() + ": waiting... received presence: " + this.presences.size() + " " + this.getPresenceString() 
					+ ", current presence: " + this.getCurrentPresenceCount());
			this.setMessage("received presence: " + this.getCurrentPresenceCount() + " / " + this.maxPresenceCount);
			Thread.sleep(3000);
		}
		
		if (this.isAllPresenceReceived) {
			log.debug(this.getUsername() + ": all presence received. thread done.");
			failed = false;
		}			
		else {
			failed = true;
		}			
		
		return failed;
	}
	
	private void processRoster() {
		// If a test doesn't include received presence, return 
		if (!presenceIncluded) return;
		
		roster.reload();
//		roster.addRosterListener(this);
		for (RosterEntry entry : roster.getEntries()) {
			String user = entry.getUser();
			
			Presence presence = roster.getPresence(user);
			Presence.Type type = presence.getType();
			
			// Add available users to the presence map
			if (type == Presence.Type.available) {
				log.debug(this.getUsername() + ": " + presence.getFrom() + " available in roster");
				presences.put(presence.getFrom(), type);
			}
		}
	}

	@Override
	protected synchronized void processPresence(Presence presence) {
		// If a test doesn't include received presence, return 
		if (!presenceIncluded) return;
		
		// Filter other users except for test users
		if (this.observer != null && presence.getFrom().contains(this.observer)) {
			log.debug(this.getUsername() + ": observer presence is received... skip this.");
			return;
		}
		
		if (!this.isAllPresenceReceived) {
			if (presence.getType() == Type.available) {
				if (this.presences.get(presence.getFrom()) == null) {
					this.presences.put(presence.getFrom(), presence.getType());
					log.debug(this.getUsername() + ": " + this.presences.size() + " presence received. From: " 
						+ presence.getFrom() + ", current presence: " + this.getCurrentPresenceCount());
				}
			}
			else {
				log.debug(this.getUsername() + ": presence is not available. From: " + presence.getFrom());
			}
			
			if (this.presences.size() >= this.maxPresenceCount || this.getCurrentPresenceCount() >= this.maxPresenceCount) {
				this.tracker.stop();
				this.isAllPresenceReceived = true;
				log.debug(this.getUsername() + ": " + this.presences.size() + " presence received. Stopped time check.");
			}
		}
	}

	public void setMaxPresenceCount(int maxPresenceCount) {
		this.maxPresenceCount = maxPresenceCount;
	}
	
	private String getPresenceString() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("{");
		for (String username : presences.keySet()) {
			sb.append("[");
			sb.append(username);
			sb.append("]");
		}
		
		sb.append("}");
		
		return sb.toString();
	}
	
	private int getCurrentPresenceCount() {
//		int currentPresenceCount = 0;
//		for (RosterEntry entry : roster.getEntries()) {
//			String user = entry.getUser();
//			
//			Presence presence = roster.getPresence(user);
//			Presence.Type type = presence.getType();
//			
//			if (type == Presence.Type.available) {
//				currentPresenceCount++;
//			}
//		}
		
//		return currentPresenceCount;
		
		return this.presences.size();
	}

	@Override
	public void entriesAdded(Collection<String> arg0) {}

	@Override
	public void entriesDeleted(Collection<String> arg0) {}

	@Override
	public void entriesUpdated(Collection<String> arg0) {}

	@Override
	public void presenceChanged(Presence presence) {
		this.processPresence(presence);
	}

	public ClientLoginThread initialize(MetaUser meta) {
		this.meta = meta;
		return this;
	}
}
